#ifndef CALLBACK
#define CALLBACK
#endif


#include <GL/glut.h>
#include <iostream>

void CALLBACK beginCallback(GLenum which)
{
   glBegin(which);
}

void CALLBACK errorCallback(GLenum errorCode)
{
   const GLubyte *estring;

   estring = gluErrorString(errorCode);
   std::cout << stderr << "Tessellation Error: %s\n" << estring <<std::endl;
   exit(0);
}

void CALLBACK endCallback(void)
{
   glEnd();
}

//void CALLBACK combineCallback(GLdouble coords[3], GLdouble *data[4], GLfloat weight[4], GLdouble **dataOut )
void CALLBACK combineCallback(GLdouble coords[3], GLdouble *, GLfloat, GLdouble **dataOut )
{
   GLdouble *vertex;
   vertex = (GLdouble *) malloc(3 * sizeof(GLdouble));

   vertex[0] = coords[0];
   vertex[1] = coords[1];
   vertex[2] = coords[2];
   *dataOut = vertex;
}



#include "face.h"
#include "object.h"

Face::Face(int _id, Object *_obj, Loop *_l) : id(_id), selected(false), object(_obj), outLoop(_l)
{
    tobj = gluNewTess();

    gluTessCallback(tobj, GLU_TESS_VERTEX, (void(*)())glVertex3dv);
    gluTessCallback(tobj, GLU_TESS_BEGIN, (void(*)())beginCallback);
    gluTessCallback(tobj, GLU_TESS_END, (void(*)())endCallback);
    gluTessCallback(tobj, GLU_TESS_ERROR, (void(*)())errorCallback);
    gluTessCallback(tobj, GLU_TESS_COMBINE, (void(*)())combineCallback);
}

Face::~Face()
{
    /**
        deletar os loops
    */
    if(outLoop) delete outLoop;
    for(unsigned int i = 0; i < inLoops.size(); i++)
        if(inLoops[i]) delete inLoops[i];
}

void Face::removeLoop(Loop *_l)
{
    if(_l == outLoop) outLoop = NULL;
    for(unsigned int i = 0; i < inLoops.size(); i++)
    {
        if(_l == inLoops[i])
        {
            inLoops.erase(inLoops.begin() + i);
            return;
        }

    }
}

Vector3D Face::normal() const
{
    /**
        pegar a normal pelo loop
    */
    return outLoop->normal();
}

double Face::area() const
{
    /**
        calcular a area do loop externo menos o interno
    */
    double result = outLoop->area();

    std::cout <<"Area da face " << id << " = " << result << std::endl;

    for(unsigned int i = 0; i < inLoops.size(); i++)
        result -= inLoops[i]->area();

    return result;
}

void Face::draw(bool _showface, bool _showNormal) const
{
    /**
        desenhar a face dependendo do loop
        as cores são dos vertices
    */
//
//    static GLdouble rects[12][3] =
//    {
//        5.0, 5.0, 0.0, 30.0, 5.0, 0.0,
//        30.0, 30.0, 0.0, 5.0, 30.0, 0.0,
//        10.0, 10.0, 0.0, 25.0, 10.0, 0.0,
//        25.0, 25.0, 0.0, 10.0, 25.0, 0.0,
//        15.0, 15.0, 0.0, 20.0, 15.0, 0.0,
//        20.0, 20.0, 0.0, 15.0, 20.0, 0.0
//    };
//
//    int i;
//    gluTessBeginPolygon(tobj, NULL);
//        gluTessBeginContour(tobj);
//        for (i = 0; i < 4; i++)
//            gluTessVertex(tobj, rects[i], rects[i]);
//        gluTessEndContour(tobj);
//        gluTessBeginContour(tobj);
//        for (i = 4; i < 8; i++)
//            gluTessVertex(tobj, rects[i], rects[i]);
//        gluTessEndContour(tobj);
//        gluTessBeginContour(tobj);
//        for (i = 8; i < 12; i++)
//            gluTessVertex(tobj, rects[i], rects[i]);
//        gluTessEndContour(tobj);
//    gluTessEndPolygon(tobj);


    HalfEdge *he1 = outLoop->getHERing(); //pega o inicio do loop
    HalfEdge *he2 = he1; //ponteiro que será usado para andar no loop, começa com o inicio

    if(he1 == he1->getNext()) return;
    if(he1 == he1->getNext()->getNext()) return;

//    std::cout << "!!!!!!!!!!! Saber como desenhar os buracos!!" << std::endl;

    int size = 0;
    Vector3D normal = outLoop->normal(), center, p;
//    if(!inLoops.empty()) normal = -normal;

    if(_showface)
    {
        gluTessBeginPolygon(tobj, NULL);

            gluTessNormal(tobj, normal.x, normal.y, normal.z);
//        glNormal3dv(normal.intoVector());
            gluTessBeginContour(tobj);
                glNormal3dv(normal.intoVector());

    }

            Vertex *v;
            do{
                v = he2->getVertex();       //pega o vertice da half-edge corrente
                p = v->getPosition();
                center = center + p;
                size++;

                if(_showface)
                {
                    if(!selected) glColor4dv(v->getColor().intoVector());
                    else glColor4f(1,0,0,0.8);
                    gluTessVertex(tobj, p.intoVector(), p.intoVector());
                }

                he2 = he2->getNext();       //anda no loop
            }while(he1 != he2);
    if(_showface)
    {
        gluTessEndContour(tobj);

        for(unsigned int i = 0; i < inLoops.size(); i++){
            he1 = inLoops[i]->getHERing();
            he2 = he1;

            if(he1 == he1->getNext()) continue;
            if(he1 == he1->getNext()->getNext()) continue;

//            Vector3D normal2 = inLoops[i]->normal();


            gluTessBeginContour(tobj);
//            glNormal3dv(normal2.intoVector());
//            gluTessNormal(tobj, normal.x, normal.y, normal.z);

            Vertex *v;
            do{
                v = he2->getVertex();       //pega o vertice da half-edge corrente
                p = v->getPosition();

                if(!selected) glColor4dv(v->getColor().intoVector());
                else glColor4f(1,0,0,0.8);
                gluTessVertex(tobj, p.intoVector(), p.intoVector());

                he2 = he2->getNext();       //anda no loop
            }while(he1 != he2);

            gluTessEndContour(tobj);
        }


        gluTessEndPolygon(tobj);
    }
    center = center * (1.0/size);
    if(_showNormal)
    {
        glBegin(GL_LINES);
            glColor3f(0, 0.2, 1);
            glVertex3dv(center.intoVector());
            glVertex3dv((center+(normal*(1.0/5.0))).intoVector());
        glEnd();
    }

}

//void Face::translate(const Vector3D &_t)
//{
//    outLoop->translate(_t);
//
//    for(unsigned int i = 0; i < inLoops.size(); i++)
//        inLoops[i]->translate(_t);
//}

std::set<Vertex *> Face::getVertices()
{
    std::set<Vertex *> result = outLoop->getVertices();

    for(unsigned int i = 0; i < inLoops.size(); i++)
    {
        std::set<Vertex *> setInLoops = inLoops[i]->getVertices();
        result.insert(setInLoops.begin(), setInLoops.end());
    }
    return result;
}

void Face::setColor(const Color &_color)
{
    /**
        muda a cor de todos os vertices
    */

    outLoop->setColor(_color);

    for(unsigned int i = 0; i < inLoops.size(); i++)
        inLoops[i]->setColor(_color);
}

void Face::selectAdjacentVertex()
{
    outLoop->selectAdjacentVertex();

    for(unsigned int i = 0; i < inLoops.size(); i++)
        inLoops[i]->selectAdjacentVertex();

    selected = false;
}

void Face::selectAdjacentEdge()
{
    outLoop->selectAdjacentEdge();

    for(unsigned int i = 0; i < inLoops.size(); i++)
        inLoops[i]->selectAdjacentEdge();

    selected = false;
}

void Face::selectAdjacentFace()
{
    outLoop->selectAdjacentFace();

    for(unsigned int i = 0; i < inLoops.size(); i++)
        inLoops[i]->selectAdjacentFace();
}
